package no.ntnu.fp.client;

import java.awt.EventQueue;
import java.io.EOFException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;

import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import no.ntnu.fp.gui.LoginGui;
import no.ntnu.fp.gui.WeeklyCalendarGui;
import no.ntnu.fp.net.co.ConnectionImpl;
import no.ntnu.fp.net.co.MessageType;
import no.ntnu.fp.net.co.NotificationType;
import no.ntnu.fp.server.Console;

/**
 * Takes care of communication with the server
 *
 */

public class CalendarClient {	
	private User loggedInAs;
	private String passwordHash;
	private ConnectionImpl serverConnection;
	private WeeklyCalendarGui calendarGUI;

	private ArrayList<User> userList;
	private ArrayList<MeetingRoom> roomList;
	private ArrayList<Appointment> appointmentList;
	private ArrayList<Notification> notificationList;

	/**
	 * Main program entry
	 */
	public static void main(String[] args) {
		try {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());}
		catch (ClassNotFoundException e) {}
		catch (InstantiationException e) {}
		catch (IllegalAccessException e) {}
		catch (UnsupportedLookAndFeelException e) {}

		//Open the login window
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new LoginGui().setVisible(true);
            }
        });        
	}

	public CalendarClient(String username, String password, String hostname, int port) throws LoginFailedException {
		String answerList[];

        //Try to connect to the server
        if( !connectToServer(hostname, port) ) return;

    	String message = MessageType.compileMessage(MessageType.MSG_LOGIN, username, password );
		String answer = sendMessageToServer(message);
		
		//Did we succeed the login?
		if( !answer.startsWith("true;") ){
			throw new LoginFailedException("Login failed!");
		}

		//Success!
		String fullname = answer.split(";")[1];
		loggedInAs = new User(username, fullname);
		passwordHash = password;
				
		//Precache a list of all the users in the database
		userList = new ArrayList<User>();
		answer = sendMessageToServer(MessageType.compileMessage(MessageType.MSG_GET_ALL_USERS));
		answerList = answer.split(";");
		for( int i = 0; i < answerList.length; i += 2 )
			userList.add( new User(answerList[i], answerList[i+1]) );
		
		//Precache a list of all the meeting rooms in the database
		roomList = new ArrayList<MeetingRoom>();
		answer = sendMessageToServer(MessageType.compileMessage(MessageType.MSG_GET_ALL_MEETING_ROOMS));
		answerList = answer.split(";");
		for( int i = 0; i < answerList.length; i += 3 )
			roomList.add( new MeetingRoom(Integer.parseInt(answerList[i]), answerList[i+1], answerList[i+2]) );

		//Precache a list of all appointments?
		appointmentList = getAllUserAppointments(loggedInAs.getUname());
				
		//Open the GUI! Finally!
		calendarGUI = new WeeklyCalendarGui(this);
	}
	
	public void displayNotifications() {
		//Get all new notifications we need to answer
		notificationList = getNotifications();
		
		//Answer each
		for( Notification notify : notificationList ) {
			Appointment which = notify.getAppointment();
			
			//uh oh... non existing appointment!
			if( which == null ) {
//				Console.printError("Warning: Notification is pointing to a non-existing Appointment!");
				continue;
			}
			String message = "You have a new notification: " + notify.toString() + " from " + which.getOwner() + "!\n";
			message += "Date: " + which.getDay() + "/" + which.getMonth() + "/" + which.getYear() + "\n";
			message += "Time: " + which.getFromTime() + " to " + which.getToTime() + "\n";
			message += "Description: " + which.getDescription() + "\n";

			//Deleted appointment notification
			if( notify.getType() == NotificationType.NOTIFY_DELETED_APPOINTMENT )
			{
				message += " \n\nThe appointment has been canceled!";
				JOptionPane.showMessageDialog(null, message, notify.toString(), JOptionPane.INFORMATION_MESSAGE);
				message = MessageType.compileMessage(MessageType.MSG_UPDATE_NOTIFICATION, String.valueOf(notify.getID()), NotificationType.NOTIFY_INACTIVE.name(), loggedInAs.getUname());
				sendMessageToServer(message);
			}

			//New appointment notification
			if( notify.getType() == NotificationType.NOTIFY_NEW_APPOINTMENT )
			{
				//Show the message
				message += " \n\nDo you accept? (Use cancel to postpone)";
				
				//Handle the response
				int respond = JOptionPane.showConfirmDialog(null, message, notify.toString(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
				switch( respond ){
					case JOptionPane.YES_OPTION:
						message = MessageType.compileMessage(MessageType.MSG_UPDATE_NOTIFICATION, String.valueOf(notify.getID()), NotificationType.NOTIFY_ACCEPT_APPOINTMENT.name(), loggedInAs.getUname());
						sendMessageToServer(message);
						continue;
					case JOptionPane.NO_OPTION:
						message = MessageType.compileMessage(MessageType.MSG_UPDATE_NOTIFICATION, String.valueOf(notify.getID()), NotificationType.NOTIFY_DECLINE_APPOINTMENT.name(), loggedInAs.getUname());
						sendMessageToServer(message);
						continue;
					default:
						//user did not want to respond at this time
			}
			}
		}		
		
		//All done!
		notificationList.clear();
	}
	
	public ArrayList<Appointment> getAllUserAppointments(String uname) {
		String answer, message;
		ArrayList<Appointment> appointments = new ArrayList<Appointment>();
		
		//Ask the server for the whole list
		message = MessageType.compileMessage(MessageType.MSG_GET_APPOINTMENTS, uname);
		answer = sendMessageToServer(message);
				
		//Did we get any result? If so parse them and translate them into Application objects
		if( !answer.isEmpty() ) {
			String result[] = answer.split(";");
			for( int i = 0; i < result.length; i += 9 ){
				int id = Integer.parseInt(result[i]);
				appointments.add( new Appointment( id, result[i+1], result[i+2], result[i+3], result[i+4], result[i+5], result[i+6], result[i+7], result[i+8] ) );
			}
		}

		return appointments;
	}

	public User getLoggedInUser(){
		return loggedInAs;
	}

	public void addAppointment(String owner, String description, MeetingRoom room, 
			String day, String month, String year, String fromTime, String toTime, ArrayList<String> participants, int row, int column) {
		// Add code for creating Appointment object, and upload the provided information to the database
		String message, answer;
		
		//Send a new request to the server to add the new appointment
		message = MessageType.compileMessage(MessageType.MSG_CREATE_APPOINTMENT, owner, description, room.getID(), day, month, year, fromTime, toTime );
		for( String participant : participants ) message = message.concat(participant.concat(";"));
		answer = sendMessageToServer(message);

		//Did something go wrong?
		if( answer.isEmpty() ) {
			JOptionPane.showMessageDialog(null, "An error occured while creating new Appointment.\nSQL: " + message,"Error",JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		//Add the new appointment to our calendar
		Appointment a = new Appointment(Integer.parseInt(answer), day, month, year, fromTime, toTime, description, room.getID(), loggedInAs.getUname());
		try {
			calendarGUI.getCalendarModel().setValueAt(a, row, column);			
		} catch (Exception e) {
			e.printStackTrace();
			Console.printConsole("something went wrong in CalendarProgram.addAppointment" + 
					"\n" +a + "\n" + row + "\n " + column);
		}
		appointmentList.add(a);
	}
	
	/**
	 * Deletes the specified appointment
	 * @param appointmentID The unique ID key number of the appointment
	 * @return true if it was successfully deleted, false otherwise
	 */
	public boolean deleteAppointment(String appointmentID) {
		String message = MessageType.compileMessage(MessageType.MSG_DELETE_APPOINTMENT, loggedInAs.getUname(), passwordHash, appointmentID);
		
		if( message.isEmpty() ){
			JOptionPane.showMessageDialog(null, "An error occured while deleting a message.\n SQL: " + message, "Error", JOptionPane.ERROR_MESSAGE);
			return false;
		}
		
		for (int i = 0; i < appointmentList.size(); i++) {
			Appointment a = appointmentList.get(i);
			if (a.getID() == Integer.parseInt(appointmentID)) {
				appointmentList.remove(a);
				break;
			}
		}
		return sendMessageToServer(message).equals("true");
	}
	
	public void editAppointment(String appointmentID, String owner, String description, MeetingRoom room, String day, String month, 
			String year, String fromTime, String toTime, ArrayList<String> participants, int row, int column) {
		
		//First delete the existing appointment
		deleteAppointment(appointmentID);
		
		//Then add the new edited version of the appointment
		addAppointment(owner, description, room, day, month, year, fromTime, toTime, participants, row, column);
	}	
	
	public ArrayList<Appointment> getDatesAppointments(int day, int month, int year) {
		
		//needs to get the given months appointments from the database
		ArrayList<Appointment> result = new ArrayList<Appointment>();
		
		//Get all appointments from the specified month
		for( Appointment a : appointmentList ) {
			if( a.getDay() == day && a.getMonth() == month && a.getYear() == year){
				result.add(a);
			}
		}
		
		//Return list of all appointments, could be empty
		return result;
	}
	
	public ArrayList<User> getUsers() {		
		return userList;
	}
	
	public String getPasswordHash(){
		return passwordHash;
	}
			
	private String sendMessageToServer( String message ) {
		
		//Send message
		try {
			serverConnection.send(message);
			
			//Wait for answer (up to 0.1 seconds)
			return serverConnection.receive(100);
		} catch (EOFException e) {
			// Receive FIN from server, initiate passive close()
			try {
				serverConnection.close();
				return "";
			} catch (IOException e1) {
			}
		} catch (ConnectException e) {
			Console.printError("Could not send message ("+message+"): " + e);
		} catch (IOException e) {
			Console.printError("Could not send message ("+message+"): " + e);
		}
				
		JOptionPane.showMessageDialog(null, "Connection lost to server!", "Error", JOptionPane.ERROR_MESSAGE);
		return "";
	}

	public void disconnectFromServer() {
		try {
			Console.printConsole("Closing connection to host '"+serverConnection.getRemoteAddress()+"' at port " + serverConnection.getLocalPort());
			serverConnection.close();
			Console.printConsole("Connection closed to host '"+serverConnection.getRemoteAddress()+"' at port " + serverConnection.getLocalPort());
		} catch (IOException e) {
		}
	}
	
	public void refreshCalendarGui() {
		calendarGUI.refresh();
	}
	
	private boolean connectToServer(String hostname, int port){
        InetAddress serverIP;

        //Connect to host
        Console.printConsole("Connecting to host '"+hostname+"' at port " + port );
		try {
	        serverConnection = new ConnectionImpl(5656);
			serverIP = InetAddress.getLocalHost();
			serverIP = InetAddress.getByName(hostname);
	        serverConnection.connect( serverIP, port );
	        return true;
		} catch (UnknownHostException e) {
			JOptionPane.showMessageDialog(null, "Host was not found.", "Could not connect to server", JOptionPane.ERROR_MESSAGE);
		} catch (SocketTimeoutException e) {
			JOptionPane.showMessageDialog(null, "Did not manage to connect after 5 attempts.", "Could not connect to server", JOptionPane.ERROR_MESSAGE);
		} catch (IOException e) {
			Console.printError("Could not connect to host: " + e);
		}		
		
		return false;
	}
	
	public ArrayList<Notification> getNotifications() {
		String message = MessageType.compileMessage(MessageType.MSG_GET_NOTIFICATIONS, loggedInAs.getUname());
		String response = sendMessageToServer(message);
		ArrayList<Notification> result = new ArrayList<Notification>();
		
		//No new notifications?
		if( response.isEmpty() ) return result;
		
		//Console.printConsole(response);
		
		//Parse each notification and add it to the list of notifications
		String[] answer = response.split(";");		
		for( int i = 0; i < answer.length; i += 3 ) {
			int id = Integer.parseInt(answer[i]);
			NotificationType type = NotificationType.valueOf(answer[i+1]);
			int appointmentID = Integer.parseInt(answer[i+2]);
			result.add( new Notification(id, type, getAppointmentByID(appointmentID)) );
		}
		return result;
	}

	/**
	 * Gets the appointment specified by the ID
	 * @param appointmentID the unique key number of the appointment
	 * @return The Appointment or null if nothing was found.
	 */
	private Appointment getAppointmentByID(int appointmentID) {
		for( Appointment a : appointmentList ){
			if(a.getID() == appointmentID) return a;
		}
		return null;
	}

	public void removeUserFromAppointment( User who ) {
		
		//Dont remove ourselves! (we are the owner)
		if( who.getUname().equals(loggedInAs.getUname()) ) {
			JOptionPane.showMessageDialog(null, "Cannot remove the owner of an appointment!", "Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		String message = MessageType.compileMessage(MessageType.MSG_REMOVE_USER_FROM_APPOINTMENT, loggedInAs.getUname(), passwordHash, who.getUname() );
		sendMessageToServer(message);
	}

	public ArrayList<MeetingRoom> getFreeRooms( String day, String month, String year, String fromTime, String toTime ) {
		ArrayList<MeetingRoom> result = new ArrayList<MeetingRoom>();
		String message = MessageType.compileMessage(MessageType.MSG_GET_FREE_MEETING_ROOMS, day, month, year, fromTime, toTime);
		String answer = sendMessageToServer(message);
		
		//No free rooms?
		if( answer.isEmpty() ) return result;
		String answerList[] = answer.split(";");
		
		//Populate list of free rooms
		for( String s : answerList ){
			for( MeetingRoom r : roomList ) {
				if( r.getID().equals(s) ){
					result.add(r);
				}
			}			
		}
		
		return result;
	}
	
	public ArrayList<User> getParticipants(int appointmentID) {
		String message = MessageType.compileMessage(MessageType.MSG_GET_PARTICIPANTS, String.valueOf(appointmentID) );
		String answer[] = sendMessageToServer(message).split(";");
		ArrayList<User> participants = new ArrayList<User>();
		ArrayList<User> allUsers = getUsers();
		for (String participant : answer) {
			for (User user : allUsers) {
				if (participant.equals(user.getUname())) {
					participants.add(user);
				}
			}
		}
		return participants;
	}
	
	public String getAppointmentReplies( int appointmentID ){
		String message = MessageType.compileMessage(MessageType.MSG_GET_APPOINTMENT_REPLY, String.valueOf(appointmentID) );
		String answer[] = sendMessageToServer(message).split(";");
		ArrayList<String> removeDuplicates = new ArrayList<String>();
		for (String a : answer) {
			if (!removeDuplicates.contains(a)) {
				removeDuplicates.add(a);
			}
		}
		if( removeDuplicates.size() <= 1 ) return "";
		
		String result = "";
		for (String s : removeDuplicates) {
			String[] notification = s.split(":");
			result += notification[0] + " \t\t " + notification[1] + "\n";
		}
		return result;
	}
	
}
